package com.gitee.wsl.io

import com.gitee.wsl.ext.number.toTwosComplementByteArray
import com.gitee.wsl.ext.string.toByteArray
import kotlinx.io.DelicateIoApi
import kotlinx.io.InternalIoApi
import kotlinx.io.Sink
import kotlinx.io.Source
import kotlinx.io.UnsafeIoApi
import kotlinx.io.unsafe.UnsafeBufferOperations
import kotlinx.io.writeDecimalLong
import kotlinx.io.writeFloat
import kotlinx.io.writeString
import kotlinx.io.writeToInternalBuffer

@OptIn(InternalIoApi::class)
val Sink.size: Int get() = buffer.size.toInt()

fun BytePacketBuilder(): Sink = kotlinx.io.Buffer()

fun Sink.append(value: CharSequence, startIndex: Int = 0, endIndex: Int = value.length) {
    writeString(value.toString(),startIndex,endIndex)
    //writeText(value, startIndex, endIndex)
}

@OptIn(InternalIoApi::class)
 fun Sink.build(): Source = buffer

 fun Sink.writeFully(buffer: ByteArray, offset: Int = 0, length: Int = buffer.size - offset) {
    write(buffer, offset, offset + length)
}

 fun Sink.writePacket(packet: Source) {
    transferFrom(packet)
}

//fun Sink.write(string: ByteString,startIndex: Int=0, endIndex: Int=this.size) = write(string.substring(startIndex,endIndex).toByteArray())

fun Sink.writeByte(byte: Int) = writeByte(byte.toByte())

fun Sink.write(int: Int) = writeInt(int)

fun Sink.write(int: Float) = writeFloat(int)

fun Sink.write(data: CharSequence) = writeText(data)

fun Sink.writeUtf8(data: CharSequence) = writeText(data)

//fun Sink.writeDecimalLong(data: Long) = writeDecimalLong(data)

/**
 * Writes [text] characters in range \[[fromIndex] .. [toIndex]) with the specified [charset]
 */
 fun Sink.writeText(
    text: CharSequence,
    fromIndex: Int = 0,
    toIndex: Int = text.length
) {
    return writeString(text.toString(), fromIndex, toIndex)
}

/**
 * Writes [text] characters in range \[[fromIndex] .. [toIndex]) with the specified [charset]
 */
 fun Sink.writeText(
    text: CharArray,
    fromIndex: Int = 0,
    toIndex: Int = text.size
) {
        val string = text.concatToString(fromIndex, fromIndex + toIndex)
        return writeString(string, 0, toIndex - fromIndex)
}


@OptIn(InternalIoApi::class)
fun <T> Sink.preview(function: (Source) -> T): T {
    return buffer.peek().use(function)
}


internal fun Sink.writeLeb128(value: Int) {
    var value = value
    while (value and 0x7f.inv() != 0) {
        writeByte((value and 0x7f) or 0x80)
        value = value ushr 7
    }
    writeByte(value)
}

internal fun Sink.writeSleb128(value: Int) {
    writeLeb128((value shl 1) xor -(value ushr 31))
}


fun Sink.writeString(string: String, charset: Charset, startIndex: Int = 0, endIndex: Int = string.length) {
    checkBounds(string.length, startIndex, endIndex)
    if (charset == Charset.UTF8) return writeString(string, startIndex, endIndex)
    val data = string.substring(startIndex, endIndex).toByteArray(charset)
    write(data, 0, data.size)
}

internal inline fun checkBounds(size: Int, startIndex: Int, endIndex: Int) =
    checkBounds(size.toLong(), startIndex.toLong(), endIndex.toLong())

internal fun checkBounds(size: Long, startIndex: Long, endIndex: Long) {
    if (startIndex < 0 || endIndex > size) {
        throw IndexOutOfBoundsException(
            "startIndex ($startIndex) and endIndex ($endIndex) are not within the range [0..size($size))"
        )
    }
    if (startIndex > endIndex) {
        throw IllegalArgumentException("startIndex ($startIndex) > endIndex ($endIndex)")
    }
}

/**
 * Directly appends [bytes] to this Sink's internal Buffer without copying. Thus, it keeps bytes managed by a Buffer accessible.
 * The bytes may be overwritten through the Buffer or even recycled to be used by another buffer.
 * Therefore, operating on these bytes after wrapping leads to undefined behaviour.
 * [startIndex] is inclusive, [endIndex] is exclusive.
 */
@OptIn(DelicateIoApi::class, UnsafeIoApi::class)
internal fun Sink.appendUnsafe(bytes: ByteArray, startIndex: Int = 0, endIndex: Int = bytes.size): Int {
    require(startIndex in 0..<endIndex) { "StartIndex must be between 0 and $endIndex" }
    writeToInternalBuffer {
        UnsafeBufferOperations.moveToTail(it, bytes, startIndex, endIndex)
    }
    return endIndex - startIndex
}

/**
 *  Encodes a positive Long to a minimum-size unsigned byte array, omitting the leading zero
 *
 *  @throws IllegalArgumentException if [number] is negative
 *  @return the number of bytes written
 */
fun Sink.writeMagnitudeLong(number: Long): Int {
    require(number >= 0)
    return number.toTwosComplementByteArray().let { appendUnsafe(it, if (it[0] == 0.toByte()) 1 else 0) }
}


/**
 * Writes a signed long using twos-complement encoding using the fewest bytes required
 *
 * @return the number of byte written to the sink
 */
fun Sink.writeTwosComplementLong(number: Long): Int = appendUnsafe(number.toTwosComplementByteArray())

/**
 * Encodes an unsigned Long to a minimum-size twos-complement byte array
 * @return the number of bytes written
 */
fun Sink.writeTwosComplementULong(number: ULong): Int = appendUnsafe(number.toTwosComplementByteArray())


/** Encodes an unsigned Int to a minimum-size twos-complement byte array
 * @return the number of bytes written to the sink
 */
fun Sink.writeTwosComplementUInt(number: UInt) = writeTwosComplementLong(number.toLong())